package processorIO;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import processor.MyException;

/**
 * A class containing methods for reading from and writing to text files.
 * 
 * Fully Tested: No
 * Fully Documented: Yes
 * Created: May 16, 2011
 * Last Modified: April 8, 2012
 * 
 * @author Shimu Wu
 */
public class FileIO {

    protected FileIO() {
    }

    // <editor-fold defaultstate="collapsed" desc="Private helpers">
    /**
     * Returns a detailed FileNotFoundException indicating which url was not 
     * found, during which operation, and also the main directory that was
     * looked in for this url.
     * @param url
     * @param operation
     * @return 
     */
    private static FileNotFoundException getFileNotFoundException(String url, String operation) {
        return new FileNotFoundException(
                String.format("Cannot find file: %s during %s operation!\nMain Directory: %s",
                url, operation, getMainDirectory()));
    }

    /**
     * Returns an IOException that contains more details about the error than 
     * the given IOException.
     * @param error_msg
     * @param e
     * @return 
     */
    private static IOException getIOException(String error_msg, IOException e, String operation) {
        return new IOException(String.format("An error occurred during %s operation!\n"
                + "%s\n"
                + "Main Directory: %s\n"
                + "%s\n", operation, error_msg, getMainDirectory(), e.getLocalizedMessage()));
    }

    private static IOException getIOException(String error_msg, String operation) {
        return new IOException(String.format("An error occurred during %s operation!\n"
                + "%s\n"
                + "Main Directory: %s\n", operation, error_msg, getMainDirectory()));
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Returns a buffered reader">
    /**
     * Returns a BufferedReader that can be used to read the content of the
     * file at the given file_path
     * @param file_path the path of the file (can be relative or absolute)
     * @return a BufferedReader that can be used to read the content of the
     * file at the given file_path
     * 
     * @throws MyException if the file cannot be found
     */
    public static BufferedReader getFileReader(String file_path)
            throws FileNotFoundException {
        try {
            return new BufferedReader(
                    new InputStreamReader(
                    new DataInputStream(
                    new FileInputStream(file_path))));

        } catch (java.io.FileNotFoundException e) {
            throw getFileNotFoundException(file_path, "read");
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Returns a buffered writer">
    /**
     * Returns a BufferedWriter object of a file at
     * directory/fileName.extension. Throws a FileProcessorException if the file does not
     * exist or if the BufferedWriter object failed to be created.
     *
     * @param file_path 
     * @return a BufferedWriter object of the file
     * 
     * @throws MyException if a file could not be created
     * @throws FileNotFoundException if the file was removed before the write
     * operation begins
     */
    public static BufferedWriter getFileWriter(String file_path,
            boolean append, String charset) throws IOException {

        File file = createFile(file_path);

        try {
            return new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(file, append), charset));
        } catch (IOException e) {
            throw getIOException(
                    String.format("Could not create buffered writer for: %s", file_path), e, "write");
        }
    }

    public static BufferedWriter getFileWriter(String file_path, boolean append)
            throws IOException {
        return getFileWriter(file_path, append, Charset.defaultCharset().name());
    }

    // </editor-fold>
    //<editor-fold defaultstate="collapsed" desc="Read operations">
    /**
     * Return the content of the text file at the given file_path as an
     * ArrayList. The returned content contains the file as it is, including
     * blank lines.
     *
     * @param file_path
     * @return an ArrayList containing the content of the text file
     * @throws MyException if the file cannot be opened or if an
     * error occurred while reading from the file
     */
    public static ArrayList<String> getContent(String file_path)
            throws FileNotFoundException, IOException {
        return getContent(getFileReader(file_path));
    }

    public static ArrayList<String> getContent(BufferedReader br)
            throws IOException {
        ArrayList<String> strList = new ArrayList<String>();

        String line;
        try {
            while ((line = br.readLine()) != null) {
                strList.add(line);
            }

        } catch (IOException e) {
            throw getIOException(String.format("An error occurred while "
                    + "reading from file"), e, "read");

        } finally {
            try {
                br.close();
            } catch (IOException e) {
                throw getIOException(
                        String.format("Cannot close file"), e, "read");
            }
        }

        return strList;
    }
    //</editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Write operations">
    /**
     * Write the given String[] into a file at the given filePath.
     * Each element in the given String[] is written on a separate line.
     *
     * @param strList an Iterable of Strings where each element is considered
     * to be on a different line
     * @param filePath the url of the file to write to
     * @param append true if the given Strings should be appended to the
     * end of the document (thereby not erasing any previous content). false
     * if the given Strings should override all the previous content in 
     * the given filePath
     * @throws MyException if the file could not be created
     */
    public static void write(Iterable<String> strList,
            BufferedWriter output, boolean close) throws IOException {

        for (String str : strList) {
            try {
                output.write(str);
                output.newLine();
            } catch (IOException e) {
                throw getIOException(
                        String.format("Unable to write line: %s", str), e, "write");
            }
        }

        if (close) {
            try {
                output.close();
            } catch (IOException e) {
                throw getIOException("Cannot close output!", e, "write");
            }
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Miscellaneous">
    /**
     * Returns the current date and time as a String in the following format:
     * yyyy/MM/dd HH:mm:ss
     * 
     * @return the current date and time as a String
     */
    public static String getDateTime() {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date date = new Date();
        return dateFormat.format(date);
    }

    /** 
     * Returns the main directory.
     * 
     * @return 
     */
    public static String getMainDirectory() {
        return System.getProperty("user.dir");
    }

    public static String getUniqueName(String url) {
        FilePath path = new FilePath(url);
        String name = url;
        int i = 2;
        while (new File(name).exists()) {
            name = new FilePath(path.getDirectoryPath(),
                    path.getFileName() + String.format("(%d)", i),
                    path.getExtension()).getFullPath();
            i++;
        }
        return name;
    }

    /**
     * Given a file_path, return a new File object that can be written to.
     * If the given path contains directories that do not exist, the directories
     * will be created. If a file with the given name already exists, returns
     * a File object that represents the existing file.
     *
     * @param file_path the path of the file (can be relative or absolute)
     * @return a File object representing the newly created file. 
     * 
     * @throws MyException if the directory/directories could not be created
     */
    public static File createFile(String file_path) throws IOException {
        final File file = new File(file_path);

        final String directory = file.getParent();
        if ((directory != null) && (!directory.isEmpty())) {
            File dir = new File(directory);

            // If the directories do not exist, then create it
            if (!dir.exists()) {

                // If make directories is not successful, return null
                if (!dir.mkdirs()) {
                    throw getIOException(directory, "create directory");
                }
            }
        }
        File newFile = new File(file_path);
        try {
            newFile.createNewFile();
        } catch (IOException e) {
            throw getIOException(file_path, e, "create file");
        }
        return newFile;
    }

    /** 
     * List the names of all the files in the given directory.
     * Throws a FileProcessorException if the given directory cannot be found.
     *
     * @param directory
     * @return
     * @throws MyException if the directory could not be found 
     */
    public static String[] listFiles(String directory) throws FileNotFoundException, IOException {
        File dir = new File(directory);
        if (!dir.exists()) {
            throw new FileNotFoundException(
                    String.format("Directory not found: %s\nMain Directory: %s",
                    directory, getMainDirectory()));
        } else if (!dir.isDirectory()) {
            throw new IOException(directory + " is not a directory!");
        }
        final String[] files = dir.list();
        return files;
    }

    /**
     * Renames the given oldFile to the given newFileName. Uses the method
     * File.renameTo(File) in the standard Java API, the only difference is 
     * errors are notified by throwing IOException. Fails if a file with the
     * new name already exists.
     * 
     * @param oldFile
     * @param newFileName
     * @param overwrite if true, then even if a file with the given newFileName
     * already exists, that file will be deleted and replaced with the given
     * oldFile. If false and a file with the given newFileName exists, then
     * an IOException is thrown
     * @throws IOException 
     */
    public static void renameTo(File oldFile, String newFileName,
            boolean overwrite)
            throws IOException {
        File newFile = new File(newFileName);
        if (newFile.exists()) {
            if (overwrite) {
                if (!newFile.delete()) {
                    throw getIOException(String.format("Failed to delete prexisting file: %s!\n", newFileName),
                            "rename");
                }
            } else {
                throw getIOException(
                        String.format("%s already exists!\n", newFileName),
                        "rename");
            }
        }
        if (!oldFile.renameTo(newFile)) {
            throw getIOException(
                    String.format("Failed to rename %s to %s!", oldFile.getName(), newFileName),
                    "rename");
        }
    }
    // </editor-fold>
}
